/*
 * Created on Aug 12, 2006
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package neuro.uu.se.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author tobias
 *
 *The Utils class provides the possiblility to run a machine dependent threaded loop on Collections, Lists and Maps. 
 *There are two ways of dividing the workload so that the threades can perform the work. When using the forEachDivided 
 *methods the work is dividedd by subdividing the provided collection into the same number of pieces as there are processing 
 *core present in the system. This means that the workstrategy must be written so that i handles a list as input. 
 *When using the forEachObject methods, each object in the collection is wrapped in a thread an the workstrategy is perfomred on 
 *each single object. This means that the workstrategy used must be able to handle a single object as input. The forEWachObject mehtods 
 *allows for getting a return value from the perfromed operations. The forEachObject mehtods return an ArrayList that holds the return values
 *from the workstrategy in hte same order as the objects in the collections provvided. In the case of a map the order of the objects is the
 *same as the order that is returned then calling map.values() 
 */


public class Utils {
	
	private static int cpus = Runtime.getRuntime().availableProcessors();
	private static ThreadPoolExecutor threadPoolExecutor;
	private static boolean init = false;
	
	/**
	 * Performs a threaded for loop on the objects in the list. The list is devided into a number of smaller parts
	 * based on the number of cpus present. The work defined by the WorkStrategy will be performed on each subpart of
	 * the collection. OBSOBS this implementation works only if no return results are required. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
	public static void forEachDivideList(WorkStrategy workStrategy, List list) throws InterruptedException, ExecutionException{
		if (!init)
			initThreadPoolExecutor();
		List <Collection> _list = getCPUCustomList(list);
		ArrayList <Future> futureList = new ArrayList <Future> ();
		for (Collection collection : _list) {
			futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, collection)));
		}
		for (Future future : futureList) {
			future.get();
		}
	}
	/**
	 * Performs a threaded for loop on the objects in the collection. The collection is devided into a number of smaller parts
	 * based on the number of cpus present. The work defined by the WorkStrategy will be performed on each subpart of
	 * the collection. OBSOBS this implementation works only if no return results are required. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
    public static void forEachDivideCollection(WorkStrategy workStrategy, Collection collection) throws InterruptedException, ExecutionException{
        if (!init)
            initThreadPoolExecutor(); 
        List <Collection> _list = getCPUCustomCollection(collection);
        ArrayList <Future> futureList = new ArrayList <Future> ();
        for (Collection subCollection : _list) {
             futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, subCollection)));
        }
        for (Future future : futureList) {
            future.get();
        }
    }
    
    /**
	 * Performs a threaded for loop on the objects in the map. The values are extracted from the map and are further
	 *  devided into a number of smaller parts based on the number of cpus present. The work defined by the WorkStrategy 
	 * will be performed on each subpart of the map. OBSOBS this implementation works only if no return results are required. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
    public static void forEachDivideMap(WorkStrategy workStrategy, Map map) throws InterruptedException, ExecutionException{
    	if (!init)
            initThreadPoolExecutor();
    	List <Collection> _list = getCPUCustomCollection(map.values());
    	ArrayList <Future> futureList = new ArrayList <Future> ();
        for (Collection subCollection : _list) {
            futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, subCollection)));
       }
       for (Future future : futureList) {
           future.get();
       }
    }
    
    /**
	 * Performs a threaded for loop on the objects in the list. For each object in the list an individual thread is created. 
	 * Each thread is run and the return results are collected before returning. On each object the work defined by WorkStrategy 
	 * is performed. OBSOBS this implemetation works for both return results and not return results. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @return
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
	public static ArrayList forEachListObject(WorkStrategy workStrategy, List list) throws InterruptedException, ExecutionException{
		if (!init)
			initThreadPoolExecutor();
		ArrayList <Future> futureList = new ArrayList <Future> ();
		for (Object object : list) {
			futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, object)));
		}
		ArrayList <Object> results = new ArrayList <Object> ();
		for (Future future : futureList) {
			results.add(future.get());
		}
		return results;
	}
	
	 /**
	 * Performs a threaded for loop on the objects in the collection. For each object in the collection an individual thread is created. 
	 * Each thread is run and the return results are collected before returning. On each object the work defined by WorkStrategy 
	 * is performed. OBSOBS this implemetation works for both return results and not return results. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @return
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
    public static ArrayList forEachCollectionObject(WorkStrategy workStrategy, Collection collection) throws InterruptedException, ExecutionException{
        if (!init)
            initThreadPoolExecutor(); 
        ArrayList <Future> futureList = new ArrayList <Future> ();
        for (Object object : collection) {
            futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, object)));
        } 
        ArrayList <Object> results = new ArrayList <Object> ();
        for (Future future : futureList) {
            results.add(future.get());
        }
        return results;
    }
	
	 /**
	 * Performs a threaded for loop on the objects in the collection. For each object in the collection an individual thread is created. 
	 * Each thread is run and the return results are collected before returning. On each object the work defined by WorkStrategy 
	 * is performed. OBSOBS this implemetation works for both return results and not return results. OBSOBS
	 * @param workStrategy
	 * @param list
	 * @return
	 * @throws InterruptedException
	 * @throws ExecutionException
	 */
    public static ArrayList forEachMapObject(WorkStrategy workStrategy, Map map) throws InterruptedException, ExecutionException{
        if (!init)
            initThreadPoolExecutor(); 
        ArrayList <Future> futureList = new ArrayList <Future> ();
        for (Object object : map.values()) {
            futureList.add(threadPoolExecutor.submit(new Worker(workStrategy, object)));
        } 
        ArrayList <Object> results = new ArrayList <Object> ();
        for (Future future : futureList) {
            results.add(future.get());
        }
        return results;
    }   
    
	
	/**
	 * Divides a list into smaller pieces depending on the number of cores present
	 * @param list
	 * @return
	 */
	private static List <Collection> getCPUCustomList(List list){
		int chuncks = (int)((double)list.size())/cpus;
		List <Collection> subLists = new ArrayList <Collection>();	
		for (int i = 0; i < cpus; i++) {
			if ( i == cpus -1 ){
				subLists.add(list.subList(i*chuncks, list.size()));
			}
			else{
				subLists.add(list.subList(i*chuncks, (i+1)*chuncks));
			}
		}
		return subLists;
	}
	
	/**
	 * Divides a collection into smaller pieces depending on the number of cores present
	 * @param collection
	 * @return
	 */
    private static List<Collection> getCPUCustomCollection(Collection collection){
        double chuncks=((double)collection.size())/cpus;
        List <Collection> subLists =new ArrayList<Collection>(); 
        for(int i=0;i<cpus;i++){
            subLists.add(new ArrayList());
        }
        int i=0;
        for (Object o : collection) {
            ((ArrayList<Object>)subLists.get((int)Math.floor((i/chuncks)))).add(o); 
        }
        return subLists;
    }
    
	/**
	 * Initializes the ThreadPoolExecutor, this is not necessary as it is initialized automaticaly by the forEach loops 
	 *
	 */
	public static void initThreadPoolExecutor(){
		init = true;
		threadPoolExecutor = new ThreadPoolExecutor(cpus, cpus, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
		threadPoolExecutor.prestartAllCoreThreads();
	}
	/**
	 * Kills the remaining threads ans awaits the termination of the ThreadPoolExecutor
	 * Necessary to stop the the ThreadPoolexecutor
	 * @throws InterruptedException
	 */
	public static void terminateThreadPoolExecutor() throws InterruptedException{
		threadPoolExecutor.shutdown();
		threadPoolExecutor.awaitTermination(50*3600, TimeUnit.SECONDS);
	}
}
 